<template>
  <div ref="canvas"></div>
</template>

<script lang="js" setup>
import * as THREE from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader"
import {ref, onMounted} from "vue";
// @ts-ignore
import testPng from '@/assets/test.png'
import {BufferAttribute} from "three";

// 画布
const canvas = ref('');
// 场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xa0a0a0);
scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
// 光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
scene.add(ambientLight);
// 相机 - 近裁截面near和远裁截面far，要能包含你想渲染的场景，否则超出视锥体模型会被剪裁掉，简单说near足够小，far足够大，主要是far。
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1, 1);
// camera.lookAt(0, 1, 0);

console.log(' import.meta: ', import.meta);
// 纹理 - new URL("../assets/test.png", import.meta.url).href testPng
const texture = new THREE.TextureLoader().load(testPng);
// 和渲染器.outputEncoding一样值 - threejs加载gltf模型，颜色贴图map属性.encoding的默认值是sRGB颜色空间THREE.sRGBEncoding
// texture.encoding = THREE.sRGBEncoding;
// 纹理对象texture.flipY默认值
console.log('texture.flipY: ', texture.flipY);
// texture.flipY = false;

// gltf模型
const gltfLoader = new GLTFLoader();
gltfLoader.load(new URL("../assets/gltf/Flower.glb", import.meta.url).href,
    (gltf) => {
      const model = gltf.scene;
      console.log('控制台查看加载gltf文件返回的对象结构: ', gltf);
      console.log('gltf对象场景属性: ', model);

      const mesh = model.children[0];
      // gltf的贴图翻转属性.flipY默认值是false - 或者map属性就是null
      console.log('model.children[0]: ', mesh, 'model.children[0].material: ', mesh.material, ' material.map: ', mesh.material?.map);
      mesh.material.map = texture;
      const geometry = model.children[0].geometry;
      // 这里有些可能是建模的时候没有设置，需要自己为几何体设置UV坐标才能在对应的面展示对应的纹理贴图
      const uv = new Float32Array([
        0, 0, // 图片左下角 - 顶点1的UV坐标
        1, 0, // 图片右下角 - 顶点2的UV坐标
        1, 1, // 图片右上角 - 顶点3的UV坐标

        0, 1, // 图片左上角 - 顶点4的UV坐标
        0, 0, // 图片左下角 - 顶点5的UV坐标
        1, 0, // 图片右下角 - 顶点6的UV坐标

        1, 1, // 图片右上角 - 顶点7的UV坐标
        0, 1, // 图片左上角 - 顶点8的UV坐标
        0, 0, // 图片左下角 - 顶点9的UV坐标

        1, 0, // 图片右下角 - 顶点10的UV坐标
        1, 1, // 图片右上角 - 顶点11的UV坐标
        0, 1, // 图片左上角 - 顶点12的UV坐标
      ]);
      geometry.attributes.uv = new BufferAttribute(uv, 2);

      // 递归遍历外部模型
      model.traverse((child) => {
        console.log("gltf.scene.traverse: ", child);
        // 递归遍历gltf所有的模型节点
        if (child.isMesh) {
          // child.castShadow = true;

          //.material.clone()返回一个新材质对象，和原来一样，重新赋值给.material属性
          // 可以解决多个mesh共享材质时，出现修改一个mesh的材质导致另外的mesh也出问题
          // child.material = child.material.clone();

          // 修改gltf所有Mesh的材质
          // child.material = new THREE.MeshStandardMaterial({
          // color: 0xffffff,
          // map: texture,
          // side: THREE.DoubleSide,
          // });
          // console.log("修改后材质", child.material);

          // 判断是否存在贴图
          // @ts-ignore
          if (child.material.map) {
            // threejs加载gltf模型，颜色贴图map属性.encoding的默认值是sRGB颜色空间THREE.sRGBEncoding
            // @ts-ignore
            console.log('child.material.map: ', child.material.map);
          }
        }
      });
      camera.lookAt(gltf.scene.position);
      // 返回的场景对象 gltf.scene 插入到 threeJs 场景中
      scene.add(gltf.scene);
    },
    (xhr) => {
      // 后台打印查看模型文件加载进度
      console.log("加载完成的百分比" + (xhr.loaded / xhr.total) * 100 + "%");
    },
    (err) => {
      console.error("加载发生错误: ", err);
    },
);
// 渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true,
});
renderer.setClearColor(0xffffff);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);

// 解决加载gltf格式模型颜色偏差问题
// .outputEncoding的默认值是线性空间THREE.LinearEncoding,和纹理对象.encoding默认值一样，
// 如果颜色贴图.encoding的值是THREE.sRGBEncoding，为了避免颜色偏差，.outputEncoding的值也需要设置为THREE.sRGBEncoding。
// renderer.outputEncoding = THREE.sRGBEncoding;

// 如果webgl渲染器设置了renderer.outputEncoding = THREE.sRGBEncoding;
// 单独加载图像返回的纹理对象需要设置 texture.encoding = THREE.sRGBEncoding;
// const texture = new THREE.TextureLoader().load('../assets/test.png');
// 和webgl渲染器renderer.outputEncoding一致
// texture.encoding = THREE.sRGBEncoding;

// 相机控件
const orbitControl = new OrbitControls(camera, renderer.domElement);
// orbitControl.target.set(100, 0, 0);
orbitControl.update();
// 渲染函数
const render = () => {
  /*
  相机控件OrbitControls旋转缩放平移本质上就是在改变相机Camera的参数。
    旋转：拖动鼠标左键
    缩放：滚动鼠标中键
    平移：拖动鼠标右键

  通过OrbitControls旋转和缩放，本质上就是在改变透视投影相机PerspectiveCamera的位置.position
  渲染循环中不停地打印相机的位置属性，可以通过相机控件旋转或缩放三维场景，同时通过浏览器控制台观察相机位置变化
  通过OrbitControls平移，OrbitControls的.target属性会发生变化，.target属性对应的就是透视投影相机PerspectiveCamera的.lookAt观察目标`
  */
  // 浏览器控制台查看相机位置变化
  // console.log('camera.position: ', camera.position);
  // 浏览器控制台查看controls.target变化，辅助设置lookAt参数
  // console.log('orbitControl.target: ', orbitControl.target);

  renderer.render(scene, camera);
  requestAnimationFrame(render);
};

onMounted(() => {
  canvas.value.appendChild(renderer.domElement);
  render();
});

window.addEventListener("resize", () => {
  // 更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
});

</script>
